package org.bigk.invoices.services;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.bigk.invoices.exceptions.ServiceException;
import org.bigk.invoices.model.Purchaser;
import org.bigk.invoices.model.PurchaserFilter;
import org.bigk.invoices.model.Purchaser_;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Service("purchasersService")
@Transactional
public class PurchasersServiceImpl implements PurchasersService {
	
	private static final Logger logger = LoggerFactory.getLogger(PurchasersServiceImpl.class);

	private static final String ALL_PURCHASERS_QUERY =
			"SELECT p FROM Purchaser p ORDER BY p.id ASC";

	@PersistenceContext
	protected EntityManager em;

	@Override
	public List<Purchaser> listAllItems() throws ServiceException {
		logger.debug("listAllItems() - start");

		TypedQuery<Purchaser> query = em.createQuery(ALL_PURCHASERS_QUERY, Purchaser.class);
		List<Purchaser> list = query.getResultList();

		logger.debug("listAllItems() - end - read [{}] element(s)", CollectionUtils.size(list));
		return list;
	}

	@Override
	public List<Purchaser> listItems4Page(PurchaserFilter filter) throws ServiceException {
		
		List<Purchaser> list = readListForFilter(filter);
		updateFilterStats(filter);
		return list;
	}

	private List<Purchaser> readListForFilter(PurchaserFilter filter) throws ServiceException {
		logger.debug("listItems4Page(filter=[{}]) - start", filter);

		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Purchaser> critQuery = critBuilder.createQuery(Purchaser.class);
		
		// select-clause
		Root<Purchaser> root = critQuery.from(Purchaser.class);
		critQuery.select(root);
		
        // where-clause
        Predicate wherePredicate = whereClauseForFilter(critBuilder, critQuery, root, filter);
        if (wherePredicate != null) {
            critQuery.where(wherePredicate);
        }
        critQuery.orderBy(critBuilder.asc(root.get(Purchaser_.name)));
        
		TypedQuery<Purchaser> query = em.createQuery(critQuery);
		
		query.setFirstResult(filter.calculateFirstResultIndex());
		query.setMaxResults(filter.getPaginatedPageSize());
		
		List<Purchaser> list = query.getResultList();
		logger.debug("listItems4Page() - end - read [{}] element(s)", CollectionUtils.size(list));
		return list;
	}
	
	private void updateFilterStats(PurchaserFilter filter) throws ServiceException {
		logger.debug("countItems4Page(filter=[{}]) - start", filter);
		
		CriteriaBuilder critBuilder = em.getCriteriaBuilder();
		CriteriaQuery<Number> critQuery = critBuilder.createQuery(Number.class);
		
		// select-clause
		Root<Purchaser> root = critQuery.from(Purchaser.class);
		critQuery.select(critBuilder.count(root));
		
        // where-clause
        Predicate wherePredicate = whereClauseForFilter(critBuilder, critQuery, root, filter);
        if (wherePredicate != null) {
            critQuery.where(wherePredicate);
        }
		
		Number count = em.createQuery(critQuery).getSingleResult();
		filter.setCalculatedTotalRecordsAndPages(count.intValue());
		
		logger.debug("countItems4Page() - items counted: [{}]", count);
	}

	private Predicate whereClauseForFilter(CriteriaBuilder critBuilder,
			CriteriaQuery<?> critQuery, Root<Purchaser> root, PurchaserFilter filter) {
		
		if (filter == null) {
			return null;
		}
		
		List<Predicate> where = new ArrayList<Predicate>();
		
		if (filter.getId() != null) {
			where.add(critBuilder.equal(root.get(Purchaser_.id), filter.getId()));
		}
		
		if (StringUtils.isNotEmpty(filter.getName())) {
			where.add(critBuilder.like(root.get(Purchaser_.name), "%" + filter.getName() + "%"));
		}
		
		if (StringUtils.isNotEmpty(filter.getNip())) {
			where.add(critBuilder.like(root.get(Purchaser_.nip), "%" + filter.getNip() + "%"));
		}

        if (where.size() > 0) {
            return critBuilder.and(where.toArray(new Predicate[0]));
        } else {
            return null;
        }
	}

	@Override
	public Purchaser getPurchaser(Long id) throws ServiceException {
		logger.debug("getPurchaser(id=[{}]) - start", + id);

		Purchaser purchaser = em.find(Purchaser.class, id);

		logger.debug("getPurchaser(Long) - end - return value=[{}]", purchaser);
		return purchaser;
	}
	
	@Override
	public void savePurchaser(Purchaser purchaser) throws ServiceException {
		logger.debug("savePurchaser(purchaser=[{}]) - start", purchaser);

		em.merge(purchaser);
		
		logger.debug("savePurchaser() - end");
	}
	
	@Override
	public void updatePurchaser(Purchaser purchaser) throws ServiceException {
		logger.debug("updatePurchaser(purchaser=[{}]) - start", purchaser);

		em.merge(purchaser);
		
		logger.debug("updatePurchaser() - end");
	}

	@Override
	public void deletePurchaser(Purchaser purchaser) throws ServiceException {
		logger.debug("deletePurchaser(purchaser=[{}]) - start", purchaser);

		em.remove(em.merge(purchaser));

		logger.debug("deletePurchaser() - end");
	}
}
